package com.ucode.springboot.starter.ESsearch.config;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.sniff.SniffOnFailureListener;
import org.elasticsearch.client.sniff.Sniffer;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import cn.hutool.core.collection.CollUtil;

public class RestHighLevelClientHelper {
    
    private RestHighLevelClient restHighLevelClient;
    private Sniffer sniffer;
    private ElasticsearchConfig properties;
    public RestHighLevelClientHelper(ElasticsearchConfig properties){
        this.properties = properties;
    }

    public RestHighLevelClient builder(){
        List<HttpHost> httpHosts = new ArrayList<>();
        List<String> clusterNodes = properties.getClusterNodes();
        if(CollUtil.isEmpty(clusterNodes)){
            throw new RuntimeException("ES node is not configured");
        }
        clusterNodes.forEach(node -> {
            try {
                String[] parts = StringUtils.split(node, ":");
                Assert.notNull(parts, "ES node configuration error");
                Assert.state(parts.length == 2, "ES node format as as 'host:port'");
                httpHosts.add(new HttpHost(parts[0], Integer.parseInt(parts[1]), properties.getSchema()));
            } catch (Exception e) {
                throw new IllegalStateException("Invalid ES nodes " + "property '" + node + "'", e);
            }
        });
        RestClientBuilder builder = RestClient.builder(httpHosts.toArray(new HttpHost[httpHosts.size()]));
        
        //设置请求头，避免每个请求都必须指定
//        Header[] defaultHeaders = new Header[]{
//                new BasicHeader("header", "value")
//        };
//        builder.setDefaultHeaders(defaultHeaders);
        
        
        //设置超时时间(毫秒级)
        builder.setRequestConfigCallback(requestConfigBuilder -> {
            requestConfigBuilder.setConnectTimeout(properties.getConnectTimeout());
            requestConfigBuilder.setSocketTimeout(properties.getSocketTimeout());
            requestConfigBuilder.setConnectionRequestTimeout(properties.getConnectionRequestTimeout());
            return requestConfigBuilder;
        });
        
        
        //设置修改 http 客户端配置的回调（例如：ssl 加密通讯，线程IO的配置，或其他任何设置）
        builder.setHttpClientConfigCallback(httpClientBuilder -> {
            //线程设置
            httpClientBuilder.setMaxConnTotal(properties.getMaxConnectTotal());
            httpClientBuilder.setMaxConnPerRoute(properties.getMaxConnectPerRoute());
            
            //身份认证
            ElasticsearchConfig.Account account = properties.getAccount();
            if (!StringUtils.isEmpty(account.getUsername())) {
                final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
                credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(account.getUsername(), account.getPassword()));
                httpClientBuilder.disableAuthCaching();
                httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
            }
            return httpClientBuilder;
        });
        
        SniffOnFailureListener sniffOnFailureListener = null;
        if(properties.getSniffercg().getEnable()){
            //自动扫描网段，使用该服务可以在配置信息中仅配置一个es地址即可
            sniffOnFailureListener = new SniffOnFailureListener();
            builder.setFailureListener(sniffOnFailureListener);
        }
        
        RestHighLevelClient restHighLevelClient = new RestHighLevelClient(builder);
        
        /**
         * 当ES服务器监听使用内网服务器IP而访问使用外网IP时，不要使用client.transport.sniff为true，在自动发现时会使用内网IP进行通信，导致无法连接到ES服务器，而直接使用addTransportAddress方法进行指定ES服务器
         * 嗅探器使用文档:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/_usage.html
         * 注意:失败时开启嗅探器，十秒刷新并更新一次节点
         */
        if(sniffOnFailureListener != null){
            Sniffer sniffer = Sniffer.builder(restHighLevelClient.getLowLevelClient())
                    .setSniffAfterFailureDelayMillis(properties.getSniffercg().getSniffAfterFailureDelayMillis())
                    .build();
            sniffOnFailureListener.setSniffer(sniffer);
        }
        return restHighLevelClient;
    }
    
    public void close(){
        if(sniffer != null){
            sniffer.close();
        }
        if(restHighLevelClient != null){
            try {
                restHighLevelClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    public ElasticsearchConfig getProperties() {
        return properties;
    }
}
